#include <time.h>
#include <cstdio>
#include <SDL2/SDL.h>
#include "minIni.h"
#include "config.h"
#include "spriteanimator.h"
#include "sprite.h"

using namespace Oribi;

Sprite::Sprite(SDL_Surface *s):
    sheet(s),
    base({0, 0})
{
    if (s)
        rect = {0, 0, s->w, s->h};
    else
        rect = {0, 0, 0, 0};

    position = {0, 0};
}

Sprite::Sprite(SDL_Surface *s, SDL_Rect r, SDL_Point p):
    sheet(s),
    rect(r),
    position(p),
    base({0, 0}){}

Sprite::Sprite(const Sprite& rhs):
    sheet(rhs.sheet),
    rect(rhs.rect),
    position(rhs.position),
    base(rhs.base)
{
    auto anEnd = rhs.animators.end();
    for (auto it = rhs.animators.begin(); it != anEnd; ++it)
    {
        if (*it)
            animators.push_front((*it)->clone());
    }

    auto sprEnd = rhs.childSprites.end();
    for (auto it = rhs.childSprites.begin(); it != sprEnd; ++it)
    {
        if (*it)
            childSprites.push_front((*it)->clone());
    }
}

Sprite *Sprite::clone() const
{
    return new Sprite(*this);
}

SDL_Point Sprite::getBasePosition() const
{
    return {position.x + base.x, position.y + base.y};
}

SDL_Rect Sprite::getDestRect() const
{
    return {position.x, position.y, rect.w, rect.h};
}

void Sprite::blit(SDL_Surface *surf) const
{
    if (!surf || !sheet)
        return;

    SDL_Rect drect = {position.x, position.y, rect.w, rect.h};
    SDL_BlitSurface(sheet, &rect, surf, &drect);

    auto end = childSprites.end();
    for (auto it = childSprites.begin(); it != end; ++it)
    {
        if (*it)
            (*it)->blitWithOffset(surf, position);
    }
}

void Sprite::blitToPosition(SDL_Surface *surf, SDL_Point pos) const
{
    if (!surf || !sheet)
        return;

    SDL_Rect drect = {pos.x, pos.y, rect.w, rect.h};
    SDL_BlitSurface(sheet, &rect, surf, &drect);

    SDL_Point offs = {position.x - pos.x, position.y - pos.y};
    auto end = childSprites.end();
    for (auto it = childSprites.begin(); it != end; ++it)
    {
        if (*it)
            (*it)->blitWithOffset(surf, offs);
    }
}

void Sprite::blitWithOffset(SDL_Surface *surf, SDL_Point offs) const
{
    if (!surf || !sheet)
        return;

    SDL_Rect drect = {position.x + offs.x, position.y + offs.y, rect.w, rect.h};
    SDL_BlitSurface(sheet, &rect, surf, &drect);

    auto end = childSprites.end();
    for (auto it = childSprites.begin(); it != end; ++it)
    {
        if (*it)
            (*it)->blitWithOffset(surf, offs);
    }
}

void Sprite::animate()
{
    int stat;
    SpriteAnimator *an;
    for (auto it = animators.begin(); it != animators.end(); it++)
    {
        if (*it)
        {
            //printf("Run animator 0x%p\n", *it);
            stat = (*it)->animateSprite(*this);
            switch (stat)
            {
            case SPR_ANIM_REMOVE:
                *it = 0;
                /* Causes segfault! Why?
                animators.erase(it);*/
                break;
            case SPR_ANIM_DESTROY:
                an = *it;
                *it = 0;
                /* Causes segfault! Why?
                animators.erase(it);*/
                delete an;
                continue;
            default:
                break;
            }
        }
    }

    auto sprEnd = childSprites.end();
    for (auto it = childSprites.begin(); it != sprEnd; ++it)
    {
        if (*it)
        {
            (*it)->animate();
        }
    }
}

SpriteAnimator *Sprite::addAnimator(const SpriteAnimator& anim)
{
    SpriteAnimator *res = anim.clone();
    animators.push_front(res);
    return res;
}

void Sprite::removeAnimator(SpriteAnimator *anim)
{
    if (anim)
        animators.remove(anim);

}

void Sprite::removeAndDeleteAnimator(SpriteAnimator *anim)
{
    if (!anim)
        return;

    animators.remove(anim);
    delete anim;
}

Sprite *Sprite::addChild(const Sprite& spr)
{
    Sprite *res = spr.clone();
    childSprites.push_front(res);
    return res;
}

void Sprite::removeChild(Sprite *spr)
{
    if (spr)
        childSprites.remove(spr);
}

bool Sprite::operator== (const Sprite& eq)
{
    return (sheet == eq.sheet
        && rect.x == eq.rect.x
        && rect.y == eq.rect.y
        && rect.w == eq.rect.w
        && rect.h == eq.rect.h
        && base.x == eq.base.x
        && base.y == eq.base.y);
}

/* Animated sprite: similar to a sprite, but with a slightly
 * different interface making character animations simpler */
AnimatedSprite::AnimatedSprite(SDL_Surface *surf):
    Sprite(surf)
{
    runningAnim = -1;
    nAnims = 0;
}

AnimatedSprite::AnimatedSprite(SDL_Surface *s, SDL_Rect q, SDL_Point p):
    Sprite(s, q, p)
{
    runningAnim = -1;
    nAnims = 0;
}

AnimatedSprite::AnimatedSprite(const AnimatedSprite& rhs):
    Sprite(rhs),
    runningAnim(rhs.runningAnim),
    animations(rhs.animations),
    nAnims(animations.size()){}

SprAnimRun *AnimatedSprite::getRunningAnimation()
{
    if (runningAnim >= 0 && runningAnim <= (int)animations.size())
        return animations[runningAnim];
    else
        return NULL;
}

void AnimatedSprite::restartAnimation()
{
    if (runningAnim >= 0 && runningAnim <= nAnims)
    {
        if (animations[runningAnim])
            animations[runningAnim]->restart();
    }
}

void AnimatedSprite::pauseAnimation()
{
    if (runningAnim >= 0 && runningAnim <= (int)animations.size())
    {
        if (animations[runningAnim])
            animations[runningAnim]->pause();
    }
}

void AnimatedSprite::runAnimation(int i)
{
    if (i >= 0 && i <= nAnims)
    {
        runningAnim = i;
        if (animations[runningAnim])
            animations[runningAnim]->run();
    }
    else if (runningAnim > -1 && runningAnim <= nAnims)
    {
        runningAnim = -1;
    }
}

Sprite *AnimatedSprite::clone() const
{
    return new AnimatedSprite(*this);
}

void AnimatedSprite::animate()
{
    int stat;
    SpriteAnimator *an;
    for (auto it = animators.begin(); it != animators.end(); it++)
    {
        if (*it)
        {
            //printf("Run animator 0x%p\n", *it);
            stat = (*it)->animateSprite(*this);
            switch (stat)
            {
            case SPR_ANIM_REMOVE:
                *it = 0;
                /* Causes segfault! Why?
                animators.erase(it);*/
                break;
            case SPR_ANIM_DESTROY:
                an = *it;
                *it = 0;
                /* Causes segfault! Why?
                animators.erase(it);*/
                delete an;
                continue;
            default:
                break;
            }
        }
    }

    if (runningAnim > -1 && runningAnim <= nAnims)
    {
        animations[runningAnim]->animateSprite(*this);
    }

    auto sprEnd = childSprites.end();
    for (auto it = childSprites.begin(); it != sprEnd; ++it)
    {
        if (*it)
        {
            (*it)->animate();
        }
    }
}

int AnimatedSprite::addAnimation(const SpriteAnimation& an)
{
    return addAnimation(SprAnimRun(an));
}

int AnimatedSprite::addAnimation(const SprAnimRun& an)
{
    int i;
    SprAnimRun *res = new SprAnimRun(an);
    for (i = 0; i < (int)nAnims; ++i)
    {
        if (!animations[i])
            animations[i] = res;
    }

    if (i == nAnims)
        animations.push_back(res);

    nAnims = animations.size();

    animations[nAnims - 1]->stop();
    return nAnims - 1;
}
void AnimatedSprite::removeAnimation(SprAnimRun* an)
{
    if (!an)
        return;

    for (int i = 0; i < nAnims; ++i)
    {
        if (animations[i] == an)
            animations[i] = NULL;
    }

    nAnims = animations.size();
}
void AnimatedSprite::removeAndDeleteAnimation(SprAnimRun *an)
{
    if (!an)
        return;

    for (int i = 0; i < nAnims; ++i)
    {
        if (animations[i] == an)
        {
            animations[i] = NULL;
            delete an;
        }
    }
    nAnims = animations.size();
}
